﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace WebChatApi.Common
{
    /// <summary>
    /// Class ReflectionUtil.
    /// </summary>
    public static class ReflectionUtil
    {
        #region Property Operations

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="classObject">The class object.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="thrownException">if set to <c>true</c> [thrown exception].</param>
        /// <returns>System.Object.</returns>
        /// <exception cref="OperationFailureException">GetPropertyValue</exception>
        public static object GetPropertyValue(object classObject, string propertyName, bool thrownException = false)
        {
            object result = null;

            try
            {
                classObject.CheckNullObject("classObject");
                propertyName.CheckNullObject("propertyName");

                var propertyInfo = classObject.GetType().GetProperty(propertyName);
                propertyInfo.CheckNullObject("propertyInfo");

                result = propertyInfo.GetValue(classObject);
            }
            catch (Exception ex)
            {
                if (thrownException)
                {
                    throw new OperationFailureException("GetPropertyValue", ex);
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="classType">Type of the class.</param>
        /// <param name="classObject">The class object.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="thrownException">if set to <c>true</c> [thrown exception].</param>
        /// <returns>``0.</returns>
        /// <exception cref="OperationFailureException">GetPropertyValue</exception>
        public static T GetPropertyValue<T>(object classObject, string propertyName, bool thrownException = false)
        {
            T result = default(T);

            try
            {
                var value = GetPropertyValue(classObject, propertyName, thrownException);

                result = (T)value;
            }
            catch (Exception ex)
            {
                if (thrownException)
                {
                    throw new OperationFailureException("GetPropertyValue", ex);
                }
            }

            return result;
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="classObject">The class object.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="value">The value.</param>
        /// <param name="thrownException">if set to <c>true</c> [thrown exception].</param>
        /// <returns><c>true</c> if succeed to set value, <c>false</c> otherwise.</returns>
        /// <exception cref="OperationFailureException">GetPropertyValue</exception>
        public static bool SetPropertyValue(object classObject, string propertyName, object value, bool thrownException = false)
        {
            bool result = true;

            try
            {
                classObject.CheckNullObject("classObject");
                propertyName.CheckNullObject("propertyName");

                var propertyInfo = classObject.GetType().GetProperty(propertyName);
                propertyInfo.CheckNullObject("propertyInfo");

                propertyInfo.SetValue(classObject, value);
            }
            catch (Exception ex)
            {
                if (thrownException)
                {
                    throw new OperationFailureException("SetPropertyValue", ex);
                }
                else
                {
                    result = false;
                }
            }

            return result;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invokes the method.
        /// </summary>
        /// <param name="classType">Type of the class.</param>
        /// <param name="classObject">The class object.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        /// <exception cref="OperationFailureException">InvokeMethod</exception>
        public static object InvokeMethod(object classObject, string methodName, params object[] parameters)
        {
            object result = null;

            try
            {
                classObject.CheckNullObject("classObject");
                methodName.CheckNullObject("methodName");

                var methodInfo = classObject.GetType().GetMethod(methodName);
                methodInfo.CheckNullObject("methodInfo");

                result = methodInfo.Invoke(classObject, parameters);
            }
            catch (Exception ex)
            {
                throw new OperationFailureException("InvokeMethod", ex);
            }

            return result;
        }

        /// <summary>
        /// Invokes the generic method.
        /// This method is used for single type generic methods in non-static class instance.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="classObject">The class object.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        public static object InvokeGenericMethod<T>(object classObject, string methodName, params object[] parameters)
        {
            return InvokeGenericMethod(classObject, methodName, new Type[] { typeof(T) }, parameters);
        }

        /// <summary>
        /// Invokes the generic method.
        /// This method is used for more than one type generic methods in non-static class instance.
        /// </summary>
        /// <param name="classObject">The class object.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="genericTypes">The generic types.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        /// <exception cref="Gbi.OperationFailureException">InvokeMethod</exception>
        /// <exception cref="OperationFailureException">InvokeMethod</exception>
        public static object InvokeGenericMethod(object classObject, string methodName, Type[] genericTypes, params object[] parameters)
        {
            object result = null;

            try
            {
                classObject.CheckNullObject("classObject");
                methodName.CheckNullObject("methodName");

                var methodInfo = classObject.GetType().GetMethod(methodName);
                methodInfo.CheckNullObject("methodInfo");

                if (genericTypes != null && genericTypes.Length > 0)
                {
                    MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericTypes);
                    result = genericMethod.Invoke(classObject, parameters);
                }
                else
                {
                    result = methodInfo.Invoke(classObject, parameters);
                }
            }
            catch (Exception ex)
            {
                throw new OperationFailureException("InvokeMethod", ex);
            }

            return result;
        }

        /// <summary>
        /// Invokes the static method.
        /// </summary>
        /// <param name="classType">Type of the class.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        public static object InvokeStaticMethod(Type classType, string methodName, params object[] parameters)
        {
            return InvokeStaticGenericMethod(classType, methodName, null, parameters);
        }

        /// <summary>
        /// Invokes the generic method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="classType">Type of the class.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        public static object InvokeStaticGenericMethod<T>(Type classType, string methodName, params object[] parameters)
        {
            return InvokeGenericMethod(classType, methodName, new Type[] { typeof(T) }, methodName, parameters);
        }

        /// <summary>
        /// Invokes the generic method.
        /// </summary>
        /// <param name="classType">Type of the class.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="genericTypes">The generic types.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>System.Object.</returns>
        /// <exception cref="Gbi.OperationFailureException">InvokeMethod</exception>
        public static object InvokeStaticGenericMethod(Type classType, string methodName, Type[] genericTypes, params object[] parameters)
        {
            object result = null;

            try
            {
                classType.CheckNullObject("classType");
                methodName.CheckNullObject("methodName");

                var methodInfo = classType.GetMethod(methodName);
                methodInfo.CheckNullObject("methodInfo");

                MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericTypes);
                if (genericMethod != null)
                {
                    result = genericMethod.Invoke(null, parameters);
                }
                else
                {
                    result = methodInfo.Invoke(null, parameters);
                }
            }
            catch (Exception ex)
            {
                throw new OperationFailureException("InvokeMethod", ex);
            }

            return result;
        }

        #endregion

        #region Create Instance

        /// <summary>
        /// Creates the new instance.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="constructorParameters">The constructor parameters.</param>
        /// <returns>``0.</returns>
        public static T CreateNewInstance<T>(params object[] constructorParameters)
        {
            return (constructorParameters == null || constructorParameters.Length == 0) ? Activator.CreateInstance<T>() : (T)(Activator.CreateInstance(typeof(T), constructorParameters));
        }

        /// <summary>
        /// Creates the new instance.
        /// </summary>
        /// <param name="classType">Type of the class.</param>
        /// <param name="constructorParameters">The constructor parameters.</param>
        /// <returns>System.Object.</returns>
        public static object CreateNewInstance(Type classType, params object[] constructorParameters)
        {
            return Activator.CreateInstance(classType, constructorParameters);
        }

        /// <summary>
        /// Creates the new instance.
        /// </summary>
        /// <param name="classType">Type of the class.</param>
        /// <param name="genericTypes">The generic types.</param>
        /// <param name="constructorParameters">The constructor parameters.</param>
        /// <returns>System.Object.</returns>
        public static object CreateNewInstance(Type classType, Type[] genericTypes, params object[] constructorParameters)
        {
            if (genericTypes != null && genericTypes.Length > 0)
            {
                var type = classType.MakeGenericType(genericTypes);
                return Activator.CreateInstance(type);
            }
            else
            {
                return CreateNewInstance(classType, constructorParameters);
            }
        }

        #endregion
    }
}
